/*
 * @file iso9660_susp.c
 *
 *  ISO9660 System Use Sharing Protocol management
 *
 */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <malloc.h>

#include <rtems.h>
#include <rtems/seterr.h>

#include "iso9660.h"
#include "iso9660_io.h"
#include "iso9660_dbg.h"
#include "iso9660_susp.h"
#include "iso9660_record.h"


int
iso9660_susp_update_fs_info(
    iso9660_fs_info_t          *fs
)
{
  int rc;
  char sector_buffer[ISO9660_SECTOR_SIZE];
  iso9660_susp_list_t susp_list;
  iso9660_susp_entry_t *susp_entry;


  rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_SUSP,
      "Reading SUSP data from sector %ld\n",
      fs->volume.root_record_location);
  rc = iso9660_read_sector(fs, fs->volume.root_record_location, 0,
      ISO9660_SECTOR_SIZE, sector_buffer);

  if (rc == -1)
    rtems_iso9660_error_and_return_minus_one(EIO,
        "susp_update_fs_info: Failed to read root record");

  rc = iso9660_susp_entry_open_dir_record(
      (iso9660_directory_record_t*)sector_buffer,
      fs, &susp_list,
      0); /* Should be 15 for CD-XA, but not supported yet */

  if (rc == -1)
    rtems_iso9660_error_and_return_minus_one(EIO,
        "susp_update_fs_info: Failed to init SUSP list");

  rc = 0;
  while (rc != RTEMS_ISO9660_SUSP_LIST_END)
  {
    rc = iso9660_susp_list_next_entry(&susp_list, &susp_entry);
    rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_SUSP,
        "SUSP entry %.2s (%x), len=%d, version=%d [rc=%d]\n",
        ISO9660_SUSP_SIGNATURE_STR(susp_entry),
        ISO9660_SUSP_SIGNATURE(susp_entry),
        ISO9660_SUSP_SIZE(susp_entry),
        ISO9660_SUSP_VERSION(susp_entry),
        rc);

    switch (ISO9660_SUSP_SIGNATURE(susp_entry))
    {
      /* Sharing protocol entry */
      case ISO9660_SUSP_SIGNATURE_SP:
        if (ISO9660_SUSP_SP_CHECK(susp_entry) == ISO9660_SUSP_SP_CHECK_VALUE)
        {
          fs->susp_skip = ISO9660_SUSP_SP_SKIP(susp_entry);
          fs->version |= ISO9660_EXTENSION_SUSP;
        }
        else
        {
          /*   Finish the detection early since this is not a proper
           * SUSP implementation
           */
          rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_SUSP,
              "invalid SUSP SP entry, check value=%x instead of %x\n",
              ISO9660_SUSP_SP_CHECK(susp_entry),
              ISO9660_SUSP_SP_CHECK_VALUE);
          iso9660_susp_list_free(&susp_list);
          return 0;
        }
      break;

      case ISO9660_SUSP_SIGNATURE_RR:
        fs->version |= ISO9660_EXTENSION_ROCK_RIDGE;
        break;

      case ISO9660_SUSP_SIGNATURE_ER:
        rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_SUSP,
            "ER entry\n");
        rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_SUSP,
            " .. identifier=`%.*s'\n",
            ISO9660_SUSP_ER_ID_LEN(susp_entry),
            ISO9660_SUSP_ER_ID(susp_entry));

        rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_SUSP,
            " .. descriptor=`%.*s'\n",
            ISO9660_SUSP_ER_DESC_LEN(susp_entry),
            ISO9660_SUSP_ER_DESC(susp_entry));
        rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_SUSP,
            " .. source=`%.*s'\n",
            ISO9660_SUSP_ER_SOURCE_LEN(susp_entry),
            ISO9660_SUSP_ER_SOURCE(susp_entry));
        break;
    }
  }

  iso9660_susp_list_free(&susp_list);
  return 0;
}


int
iso9660_susp_entry_open_dir_record(
    iso9660_directory_record_t *record,
    iso9660_fs_info_t          *fs,
    iso9660_susp_list_t        *list,
    uint8_t                     skip
)
{
  return iso9660_susp_list_init(list, fs, record, ISO9660_SECTOR_SIZE,
      ISO9660_RECORD_SYSTEM_USE_OFS(record) + skip, 0);
}


int
iso9660_susp_entry_open_record(
    iso9660_record_t      *record,
    iso9660_susp_list_t   *list
)
{
  return iso9660_susp_list_init(list, record->fs, record->system_use,
      record->system_use_size, 0, 0);
}

int
iso9660_susp_list_init(
    iso9660_susp_list_t  *list,
    iso9660_fs_info_t    *fs,
    void                 *space,
    uint16_t              size,
    uint16_t              offset,
    uint8_t               flags
)
{
  list->fs = fs;
  list->data = space;
  list->size = size;
  list->next_entry_offset = offset;
  list->flags = flags;

  return 0;
}

int
iso9660_susp_list_free(
    iso9660_susp_list_t  *list
)
{
  if (list->flags & RTEMS_ISO9660_SUSP_LIST_HEAP_DATA)
    free(list->data);

  return 0;
}

int
iso9660_susp_list_next_entry(
    iso9660_susp_list_t   *list,
    iso9660_susp_entry_t  **entry
)
{
  int rc;

  *entry = (uint8_t*)list->data + list->next_entry_offset;

  list->next_entry_offset += ISO9660_SUSP_SIZE(*entry);

  /* This is a CE entry which mean we have to look for
   * other entries elsewhere on the volume
   */
  if (ISO9660_SUSP_SIGNATURE(*entry) == ISO9660_SUSP_SIGNATURE_CE)
  {
    iso9660_sector_t susp_next_buffer = ISO9660_SUSP_CE_LOCATION(*entry);

    rtems_iso9660_dbg_trace(RTEMS_ISO9660_DEBUG_SUSP,
        "SUSP CE entry, continuing to sector %ld.\n", susp_next_buffer);
    list->flags = RTEMS_ISO9660_SUSP_LIST_HEAP_DATA;
    list->next_entry_offset = ISO9660_SUSP_CE_OFFSET(*entry);
    list->size =  ISO9660_SUSP_CE_LENGTH(*entry);

    /* Automatic freeing of data if it was allocated on the heap */
    iso9660_susp_list_free(list);

    list->data = malloc(ISO9660_SECTOR_SIZE * sizeof(char));
    if (list->data == NULL)
      rtems_iso9660_error_and_return_minus_one(ENOMEM,
          "susp_list_next_entry: Failed to allocate memory for continuation area");

    rc = iso9660_read_sector(list->fs, susp_next_buffer, 0,
        ISO9660_SECTOR_SIZE, list->data);
    if (rc == -1)
      rtems_iso9660_error_and_return_minus_one(EIO,
        "susp_list_next_entry: Failed to read continuation area");

    return iso9660_susp_list_next_entry(list, entry);
  }

  if (list->next_entry_offset + 4 > list->size)
    return RTEMS_ISO9660_SUSP_LIST_END;

  if (ISO9660_SUSP_VERSION(*entry) == 0)
    return RTEMS_ISO9660_SUSP_LIST_END;

  if (ISO9660_SUSP_SIGNATURE(*entry) == ISO9660_SUSP_SIGNATURE_ST)
    return RTEMS_ISO9660_SUSP_LIST_END;

  return 0;
}


int
iso9660_susp_list_search(
    iso9660_susp_entry_t **result,
    uint16_t               signature,
    iso9660_susp_list_t   *list
)
{
  int rc = 0;

  while (rc != RTEMS_ISO9660_SUSP_LIST_END && rc != -1)
  {
    rc = iso9660_susp_list_next_entry(list, result);
    if (rc < 0)
      rtems_iso9660_error_and_return_minus_one(errno,
          "susp_list_search: Failed to retrieve next entry in list");

    if (ISO9660_SUSP_SIGNATURE(*result) == signature)
      return 0;
  }

  errno = 0;
  return -1;
}
